#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>

#include <ev.h>

#include "user.h"
#include "http.h"
#include "main.h"
#include "logger.h"
#include "netutils.h"
#include "resolve.h"
#include "callback.h"
#include "socks5.h"
#include "optparser.h"
#include "help.h"
#include "dynctl.h"

GlobalInfo *g = NULL;
uint8_t basemac[6];

#define EPOLL_SIZE 1024
#define LISTEN_BACKLOG 128
#define MAX_EVENTS_COUNT 128
#define LISTEN_PORT 5000
#define DYNCTL_PORT 5003

static void signal_handler(int sig) {
    logger_info("receive signal: [%d]\n", sig);

    switch (sig) {
        case SIGINT:
        case SIGTERM:
            break;
        default:
            logger_warn("unknown signal [%d]\n", sig);
    }
}

static int register_signals() {
    // ctrl + c
    if (SIG_ERR == signal(SIGINT, signal_handler)) {
        logger_error("register signal SIGINT fail\n");
        return -1;
    }

    // pkill
    if (SIG_ERR == signal(SIGTERM, signal_handler)) {
        logger_error("register signal SIGTERM fail\n");
        return -1;
    }

    return 0;
}

int create_and_bind(char *addr, uint16_t port, int32_t backlog) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        logger_error("sockfd create failed");
        return -1;
    }

    struct sockaddr_in servaddr;
    memset((char *)&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(addr);
    servaddr.sin_port = htons(port);

    if (set_nonblocking(sockfd) < 0) {
        logger_error("set_nonblocking fail [%d]\n", errno);
        close(sockfd);
        return -1;
    }

    if (set_reuseaddr(sockfd) < 0) {
        logger_error("set_reuseaddr fail [%d]\n", errno);
        close(sockfd);
        return -1;
    }

    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        logger_error("bind error [%d]\n", errno);
        close(sockfd);
        return -1;
    }

    if (listen(sockfd, backlog) < 0) {
        logger_error("listen error [%d]!\n", errno);
        close(sockfd);
        return -1;
    }

    return sockfd;
}

uint16_t base_port = 3000;

struct socks5_server g_server = {
    0,
    "",
    0,
    "",
    "",
    "",
    LISTEN_PORT,
    DYNCTL_PORT,
    SOCKS5_AUTH_USERNAMEPASSWORD,
    false,
    LOGGER_LEVEL_INFO
};

int serverbind(struct socks5_server *gs)
{
    struct ev_loop *loop = ev_default_loop(0);
    struct ev_io *server_watcher = malloc(sizeof(struct ev_io));
    server_watcher->fd = create_and_bind(gs->addr, gs->port, LISTEN_BACKLOG);
    server_watcher->data = gs;
    if (server_watcher->fd < 0) {
        logger_error("create_and_bind fail, errno: [%d]\n", errno);
	free(server_watcher);
	return -1;
    }

    /* ip */
    int listenfd;
    listen_ctx_t *dynctl_ip_ctx = malloc(sizeof(listen_ctx_t));
    listenfd = create_and_bind(gs->addr, gs->dynctl_port, LISTEN_BACKLOG);
    if(listenfd == -1) {
    	logger_error("bind error\n");
	free(server_watcher);
	free(dynctl_ip_ctx);
	return -1;
    }
    dynctl_ip_ctx->fd = listenfd;
    dynctl_ip_ctx->loop = loop;

    gs->listenfd = server_watcher;
    gs->listenctlfd = dynctl_ip_ctx;

    ev_io_init(server_watcher, accept_cb, server_watcher->fd, EV_READ);
    ev_io_start(loop, server_watcher);
    logger_info("start working, port: [%d], auth_method: [%d]\n", gs->port, gs->auth_method);

    ev_io_init(&(dynctl_ip_ctx->io), accept_dynctl_cb, listenfd, EV_READ);
    ev_io_start(loop, &(dynctl_ip_ctx->io));
    logger_info("dynsock: listen dynctl port %d\n", gs->dynctl_port);
    return 0;
}

int nicmac(char *nic, uint8_t *mac)
{
	int ret = 0;
	struct ifreq req;
	strcpy(req.ifr_name, nic);
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0) {
		logger_error("Create socket failed\n");
		exit(-1);
	}
	ret = ioctl(sockfd, SIOCGIFHWADDR, &req);
	if(ret < 0) {
		logger_error("Get ap mac failed\n");
		ret = -1;
		goto end;
	}
	memcpy(mac, req.ifr_hwaddr.sa_data, 6);
end:
	close(sockfd);
	return ret;
}

int nicip(char *nic, struct sockaddr_in *addr)
{
	int ret = 0;
	struct ifreq req;
	strcpy(req.ifr_name, nic);
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0) {
		logger_error("Create socket failed\n");
		exit(-1);
	}
	ret = ioctl(sockfd, SIOCGIFADDR, &req);
	if(ret < 0) {
		logger_error("Get ap ip failed\n");
		ret = -1;
		goto end;
	}
	logger_debug("nic ip: %s\n", 
		inet_ntoa(((struct sockaddr_in*)&req.ifr_addr)->sin_addr));
	memcpy(addr, (void *)&req.ifr_addr, sizeof(struct sockaddr_in));
end:
	close(sockfd);
	return ret;
}

struct nic_t *niclist = NULL;
int _nic_del(struct nic_t *newnic)
{
	struct socks5_server *gs = NULL;
	struct ev_loop *loop = ev_default_loop(0);

	struct urluser *pos;
	list_for_each_entry(pos, &urluser_head, node) {
		char data[1024];
		char url[1024];
		snprintf(data, 1024, "{\"socks5ser\":\"%s\",\"socks5port\":\"5000\",\"dynctlport\":\"5003\",\"status\":\"0\"}", newnic->gs->addr);
		snprintf(url, 1024, "%s/nameserver/api/socks5ser",
			pos->url);
		printf("url: %s, data: %s\n", url, data);
		http_multi_send(g, url, data, NULL, NULL, NULL);
	}

	gs = newnic->gs;
	if(gs->listenfd) {
		ev_io_stop(loop, gs->listenfd);
		close(gs->listenfd->fd);
		free(gs->listenfd);
	}
	if(gs->listenctlfd) {
		ev_io_stop(loop, &(gs->listenctlfd->io));
		close(gs->listenctlfd->fd);
		free(gs->listenctlfd);
	}

	HASH_DEL(niclist, newnic);
	free(newnic->gs);
	free(newnic);
	return 0;

}

void nic_flush(char *host)
{
	struct nic_t *s, *tmp;
	HASH_ITER(hh, niclist, s, tmp) {
		char data[1024];
		char url[1024];
		snprintf(data, 1024, "{\"serverid\":\"%02x:%02x:%02x:%02x:%02x:%02x\",\"socks5ser\":\"%s\",\"socks5port\":\"5000\",\"dynctlport\":\"5003\",\"status\":\"1\"}",
			basemac[0],
			basemac[1],
			basemac[2],
			basemac[3],
			basemac[4],
			basemac[5],
			s->gs->addr);
		snprintf(url, 1024, "%s/nameserver/api/socks5ser",
			host);
		printf("url: %s, data: %s\n", url, data);

		http_multi_send(g, url, data, NULL, NULL, NULL);
	}
}

int nic_del(char *nic)
{
	struct nic_t *newnic = NULL;
	HASH_FIND(hh, niclist, nic, strlen(nic), newnic);
	if(newnic != NULL) {
		return _nic_del(newnic);
	}
	return -1;
}

struct nic_t *nic_add(char *nic)
{
	struct socks5_server *gs = NULL;
	struct nic_t *newnic = NULL;
	struct ev_loop *loop = ev_default_loop(0);
	HASH_FIND(hh, niclist, nic, strlen(nic), newnic);
	if(newnic == NULL) {
		newnic = malloc(sizeof(struct nic_t));
		strncpy(newnic->nic, nic, MAXNICLEN - 1);
		if(nicip(nic, &newnic->addr) < 0) {
			free(newnic);
			return NULL;
		}
		if(nicmac(nic, &(newnic->mac[0])) < 0) {
			free(newnic);
			return NULL;
		}
		gs = malloc(sizeof(struct socks5_server));
		memcpy(gs, &g_server, sizeof(g_server));
		gs->addr = inet_ntoa(newnic->addr.sin_addr);
		gs->port= LISTEN_PORT;
		newnic->gs = gs;

		HASH_ADD(hh, niclist, nic, strlen(newnic->nic), newnic);
	} else {
		struct urluser *pos;
		list_for_each_entry(pos, &urluser_head, node) {
			char data[1024];
			char url[1024];
			snprintf(data, 1024, "{\"socks5ser\":\"%s\",\"socks5port\":\"5000\",\"dynctlport\":\"5003\",\"status\":\"0\"}", newnic->gs->addr);
			snprintf(url, 1024, "%s/nameserver/api/socks5ser",
				pos->url);
			printf("url: %s, data: %s\n", url, data);
			http_multi_send(g, url, data, NULL, NULL, NULL);
		}

		if(nicip(nic, &newnic->addr) < 0) {
			_nic_del(newnic);
			return NULL;
		}
		if(nicmac(nic, &(newnic->mac[0])) < 0) {
			_nic_del(newnic);
			return NULL;
		}

		gs = newnic->gs;
		if(gs->listenfd) {
			ev_io_stop(loop, gs->listenfd);
			close(gs->listenfd->fd);
			free(gs->listenfd);
		}
		if(gs->listenctlfd) {
			ev_io_stop(loop, &(gs->listenctlfd->io));
			close(gs->listenctlfd->fd);
			free(gs->listenctlfd);
		}

		memcpy(gs, &g_server, sizeof(g_server));
		gs->addr = inet_ntoa(newnic->addr.sin_addr);
		gs->port= LISTEN_PORT;
	}
	return newnic;
}

void shownic(EV_P_ buffer2_t *buf)
{
	char *addrstr;
	char macstr[18];
	struct nic_t *s, *tmp;
	breset(buf);
	HASH_ITER(hh, niclist, s, tmp) {
		addrstr = inet_ntoa(s->addr.sin_addr);
		snprintf(macstr, 18, "%02x:%02x:%02x:%02x:%02x:%02x",
			s->mac[0], s->mac[1], s->mac[2],
			s->mac[3], s->mac[4], s->mac[5]);

		bconcat(buf, "\t", 1);
		bconcat(buf, s->nic, strlen(s->nic));
		bconcat(buf, "\t", 1);
		bconcat(buf, macstr, 17);
		bconcat(buf, "\t", 1);
		bconcat(buf, addrstr, strlen(addrstr));
		bconcat(buf, "\n", 1);
	}
}

int reconnect_del(char *nic)
{
	return nic_del(nic);
}

int reconnect_add(char *nic)
{
	struct nic_t *newnic;
	char *pnic = strdup(nic);
	if((newnic = nic_add(pnic)) == NULL) {
		free(pnic);
		return -1;
	}

	if(serverbind(newnic->gs) < 0) {
		_nic_del(newnic);
		return -1;
	} else {
		struct urluser *pos;
		list_for_each_entry(pos, &urluser_head, node) {
			char data[1024];
			char url[1024];
			snprintf(data, 1024, "{\"serverid\":\"%02x:%02x:%02x:%02x:%02x:%02x\",\"socks5ser\":\"%s\",\"socks5port\":\"5000\",\"dynctlport\":\"5003\",\"status\":\"1\"}",
				basemac[0],
				basemac[1],
				basemac[2],
				basemac[3],
				basemac[4],
				basemac[5],
				newnic->gs->addr);
			snprintf(url, 1024, "%s/nameserver/api/socks5ser",
				pos->url);
			printf("url: %s, data: %s\n", url, data);

			http_multi_send(g, url, data, NULL, NULL, NULL);
		}
	}
	return 0;
}

void nic_init()
{
	if(g_server.nic && strlen(g_server.nic) > 0) {
	} else {
		logger_error("specify nic\n");
		exit(-1);
	}

	int ret;
	struct ifreq ifr;

	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0) {
		logger_error("create socket failed: %s(%d)\n",
			strerror(errno), errno);
		exit(-1);
	}

	strcpy(ifr.ifr_name, g_server.nic);
	ret = ioctl(sockfd, SIOCGIFHWADDR, &ifr);
	if(ret != 0) {
		logger_error("ioctl failed: %s(%d)\n",
			strerror(errno), errno);
		exit(-1);
	}
	memcpy(basemac, ifr.ifr_hwaddr.sa_data, 6);
	printf("nic: %s, mac: %02x:%02x:%02x:%02x:%02x:%02x\n",
		g_server.nic,
		basemac[0],
		basemac[1],
		basemac[2],
		basemac[3],
		basemac[4],
		basemac[5]
	      );
	close(sockfd);
}

int main (int argc, char **argv) {
	if (socks5_server_parse(argc, argv) < 0) {
		help();
		exit(EXIT_FAILURE);
	}

	logger_init(NULL, g_server.log_level | LOGGER_COLOR_ON);

	if (g_server.daemon) {
		if (daemon(1, 0) < 0) {
			logger_error("daemon fail, errno [%d]\n", errno);
			exit(EXIT_FAILURE);
		}
	}

	logger_info("starting ...\n");

	struct ev_loop *loop = ev_default_loop(0);

	if (resolve_init(loop, NULL, 0) < 0) {
		logger_error("resolve_init fail\n");
		exit(EXIT_FAILURE);
	} else {
		logger_info("resolve_init ok\n");
	}

	// if (register_signals() < 0) {
	//     logger_error("register_signals fail, errno: [%d]\n", errno);
	//     exit(EXIT_FAILURE);
	// }
	//
	/* ip */
	int listenfd;
	listen_ctx_t *dynctl_ip_ctx = malloc(sizeof(listen_ctx_t));
	listenfd = create_and_bind("127.0.0.1", base_port, LISTEN_BACKLOG);
	if(listenfd == -1) {
		logger_error("bind error\n");
		exit(1);
	}
	dynctl_ip_ctx->fd = listenfd;
	dynctl_ip_ctx->loop = loop;
	ev_io_init(&(dynctl_ip_ctx->io), accept_dynctl_cb, listenfd, EV_READ);
	ev_io_start(loop, &(dynctl_ip_ctx->io));
	logger_info("dynsock: listen dynctl port %d\n", base_port);

	/* http multi */
	g = http_multi_init(EV_A);

	nic_init();

	ev_run(loop, 0);

	logger_info("exiting ...\n");

	resolve_shutdown(loop);
	ev_loop_destroy(loop);

	return EXIT_SUCCESS;
}
